package repository

import (
	"context"
	"fmt"
	"github.com/go-xorm/xorm"
	"gluenet/internal/glue-srvp/adapter/repository/etcd"
	"gluenet/internal/glue-srvp/adapter/repository/helm"
	"gluenet/internal/glue-srvp/adapter/repository/mysql"
	"gluenet/internal/glue-srvp/domain/dependency"
	"gluenet/internal/glue-srvp/domain/entity"
	"gluenet/internal/pkg/infra"
	"gluenet/internal/pkg/infra/prepare"
	etcdClient "go.etcd.io/etcd/client/v3"
	"gopkg.in/yaml.v3"
)

func init() {
	prepare.RegisterRepo(func() {
		repo := NewMetaRepo()
		prepare.InjectDependency(func() dependency.MetaRepo {
			return repo
		})
	})
}

func NewMetaRepo() *Meta {
	r := &Meta{}
	return r
}

// Meta 是对domain/dependency的MetaRepo的一个实现，具体函数功能请参见接口定义
type Meta struct{}

func (m Meta) Add(obj infra.Entity) (infra.Entity, error) {
	var err error

	// 实例是config的可能需要上传文件
	if obje, ok := obj.(*entity.Config); ok {
		if obje.ConfigFile.IsUploads {
			info, err := helm.DB.SaveCharts(obje.ConfigFile.UserId, obje.ConfigFile.FileName, obje.ConfigFile.Reader)
			if err != nil {
				return nil, err
			}
			obje.Name = info.Name
			obje.Describe = info.Describe
			obje.Version = "1"
			obje.Kind = infra.ConfigKindHelmChart
			obje.DetailRepoValue.Charts = info.Detail
			obje.DetailRepoValue.OptionJaeger = infra.Enable
			obje.DetailRepoValue.OptionLinkerd = infra.Enable
			obj = obje
		}
	}

	// 元数据存储到mysql
	if _, err = mysql.DB.InsertOne(obj); err != nil {
		return nil, err
	}
	return obj, nil
}

func (m Meta) Del(obj infra.Entity) (infra.Entity, error) {
	// 从mysql中删除元数据
	if _, err := mysql.DB.Delete(obj); err != nil {
		return nil, err
	}
	return obj, nil
}

//func (m Meta) DelBy(obj infra.Entity, query map[string]interface{}) error {
//	return nil
//}

func (m Meta) Upt(obj infra.Entity) (infra.Entity, error) {
	var err error

	// 元数据存储到mysql
	if _, err = mysql.DB.Update(obj); err != nil {
		return nil, err
	}
	return obj, nil
}

func (m Meta) FindOne(obj interface{}, query interface{}, arg ...interface{}) error {
	ok, err := mysql.DB.Where(query, arg).Get(obj)
	if err != nil {
		return err
	}
	if !ok {
		return infra.DataNotFind
	}
	return nil
}

func (m Meta) FindBy(objs interface{}, query interface{}, arg ...interface{}) error {
	if err := mysql.DB.Where(query, arg).Find(objs); err != nil {
		return err
	}
	return nil
}

func (m Meta) FindAll(objs interface{}) error {
	return m.FindBy(objs, map[string]interface{}{})
}

func (m Meta) FindKV(key string, value interface{}) error {
	if key == "" {
		return fmt.Errorf("invalid key null")
	}
	res, err := etcd.DB.Get(context.Background(), key)
	if err != nil {
		return err
	}
	if len(res.Kvs) > 1 {
		return fmt.Errorf("many matching keys")
	}
	if len(res.Kvs) == 0 {
		return infra.DataNotFind
	}

	if err = yaml.Unmarshal(res.Kvs[0].Value, value); err != nil {
		return err
	}
	return nil
}

func (m Meta) PutKV(key string, value interface{}) error {
	bts, err := yaml.Marshal(value)
	if err != nil {
		return err
	}
	_, err = etcd.DB.Put(context.Background(), key, string(bts))
	return err
}

func (m Meta) DelKV(key string) error {
	_, err := etcd.DB.Delete(context.Background(), key)
	return err
}

func (m Meta) DelWithPrefix(prefix string) error {
	if prefix == "" {
		return fmt.Errorf("key is null")
	}
	_, err := etcd.DB.Delete(context.Background(), prefix, etcdClient.WithPrefix())
	if err != nil {
		return err
	}
	return nil
}

func (m Meta) FindKVsWithPrefix(prefix string, values interface{}) error {
	panic("implement me")
}

func (m Meta) FindKeysWithPrefix(prefix string, keys interface{}) error {
	if prefix == "" {
		return infra.DataIsNull
	}
	ks, ok := keys.(*[]string)
	if !ok {
		return infra.UnKnownKind
	}

	res, err := etcd.DB.Get(context.Background(), prefix, etcdClient.WithPrefix(), etcdClient.WithKeysOnly())
	if err != nil {
		return err
	}
	if len(res.Kvs) == 0 {
		return infra.DataNotFind
	}

	var value = make([]string, len(res.Kvs))
	for k, v := range res.Kvs {
		value[k] = string(v.Key)
	}
	(*ks) = append((*ks), value...)

	return nil
}

//func (i *Detail) Watch(repoKey string) (<-chan *event2.RepoWatch, context.CancelFunc) {
//	ctx := context.Background()
//	ctxNew, cancel := context.WithCancel(ctx)
//	ch := etcd2.DB.Watch(ctxNew, repoKey, etcdClient.WithPrefix())
//
//	rch := make(chan *event2.RepoWatch)
//	go func() {
//		for {
//			select {
//			case <-ctxNew.Done():
//				close(rch)
//				return
//			case data := <-ch:
//				for _, e := range data.Events {
//					wh := &event2.RepoWatch{
//						RepoKey:   string(e.Kv.Key),
//						RepoValue: e.Kv.Value,
//					}
//					switch e.Type {
//					case etcdClient.EventTypeDelete:
//						wh.EventType = event2.DELETE
//					case etcdClient.EventTypePut:
//						wh.EventType = event2.PUT
//					}
//					rch <- wh
//				}
//			}
//		}
//	}()
//	return rch, cancel
//}

func (r Meta) Engine() *xorm.Engine {
	return mysql.DB
}

func (r Meta) ListRepoCharts(repo string, url string) (map[string][]infra.ChartInfoSimple, error) {
	return helm.DB.ListRepoChart(repo, url)
}
func (r Meta) ImportRepoChart(repo string, chart string, version string, userId string) (*infra.HelmInfo, error) {
	return helm.DB.ImortRepoChart(repo, chart, version, userId)
}
